<?php
defined('IN_XLP') or die('Access Denied');

/* 
 * 2015-02-04 支付接口升级
 * 微信统一支付接口 包括JSAPI，NATIVE，APP
 * 文档地址：http://mch.weixin.qq.com/wiki/doc/api/index.php?chapter=9_1
 */
class weixinUnipay {

    //统一下单地址
    const ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    //交易类型
    private static $trade_type = array('JSAPI','NATIVE','APP');
    //微信基本参数
    private $appid;
    private $mch_id;    //原partner_id
    private $partner_key;
    private $paysign_key;

    //支付通知URL
    private $notify_url;

    public function __construct($p=array()) {
        $this->notify_url  = U('uninotify/index');
        
        $this->appid       = isset($p['appid']) ? $p['appid'] : '';
        $this->mch_id      = isset($p['partner_id']) ? $p['partner_id'] : '';
        $this->partner_key = isset($p['partner_key']) ? $p['partner_key'] : '';
        $this->paysign_key = isset($p['paysign_key']) ? $p['paysign_key'] : '';

        if( !($this->appid && $this->mch_id && $this->partner_key && $this->paysign_key) ) {

            $userInfo = F('weixin/setting');

            if (isHave($userInfo['user_info']['wx_AppId']) 
                && isHave($userInfo['user_info']['wx_PartnerId'])
                && isHave($userInfo['user_info']['wx_PartnerKey'])
                && isHave($userInfo['user_info']['wx_PaySignKey'])) {

                //设置微信基本参数
                $this->appid       = $userInfo['user_info']['wx_AppId'];
                $this->mch_id      = $userInfo['user_info']['wx_PartnerId'];
                $this->partner_key = $userInfo['user_info']['wx_PartnerKey'];
                $this->paysign_key = $userInfo['user_info']['wx_PaySignKey'];

            } else {
                throw new Exception("Error:请设置好所有的微信参数");
            }

        }
    }

    /**
     * 向统一下单地址post xml数据 成功后获得prepay_id
     * 
     * @param  array  $order_params 订单各类参数
     * @param  int    $type         交易类型
     * @return
     */
    public function order($order_params=array()) {
        //参数过滤
        $order_params = $this->orderParamFilter($order_params);

        $nonce_str    = getRandStr();   //32位以下的随机字符串
        $body         = $order_params['body'];  //订单描述
        $out_trade_no = $order_params['out_trade_no'];  //外部交易号
        $total_fee    = $order_params['total_fee'];  //订单实付金额，单位为分
        // $ip           = getUserIp();
        $ip           = $_SERVER['REMOTE_ADDR'];    //用户ip
        $trade_type   = $order_params['trade_type']; //交易类型
        $sign         = $this->getMD5Sign($order_params + array(
                            'appid'            => $this->appid,
                            'mch_id'           => $this->mch_id,
                            'nonce_str'        => $nonce_str,
                            'spbill_create_ip' => $ip,
                            'notify_url'       => $this->notify_url,
                            'trade_type'       => $trade_type,
                        ));   //参数签名

        //其它参数
        $others = "";
        unset(
            $order_params['body'],
            $order_params['out_trade_no'],
            $order_params['total_fee'],
            $order_params['trade_type']
        );
        if($order_params) {
            foreach ($order_params as $k => $v) {
                $others .= "<{$k}>{$v}</{$k}>";
            }
        }
        

        //最终POST数据
        $xml = 
        "<xml>
            <appid>{$this->appid}</appid>
            <mch_id>{$this->mch_id}</mch_id>
            <nonce_str>{$nonce_str}</nonce_str>
            <sign>{$sign}</sign>
            <body>{$body}</body>
            <out_trade_no>{$out_trade_no}</out_trade_no>
            <total_fee>{$total_fee}</total_fee>
            <spbill_create_ip>{$ip}</spbill_create_ip>
            <notify_url>{$this->notify_url}</notify_url>
            <trade_type>{$trade_type}</trade_type>
            {$others}
        </xml>";

        $result = getHttp(self::ORDER_URL, $xml);

        if($result) {
            $data = (array)simplexml_load_string($result, 'SimpleXMLElement', LIBXML_NOCDATA);
            if($data['return_code']==='SUCCESS') {
                //验证通知签名
                $this->checkReturnMsg($data);
                if($data['result_code']==='SUCCESS') {
                    if($data['trade_type']==='NATIVE') {    //原生支付
                        return array(
                            'trade_type' => $data['trade_type'],
                            'prepay_id'  => $data['prepay_id'],
                            'code_url'   => $data['code_url'],
                        );
                    } else {
                        return array(
                            'trade_type' => $data['trade_type'],
                            'prepay_id'  => $data['prepay_id'],
                        );
                    }
                } else {
                    throw new Exception("{$data['err_code']} {$data['err_code_des']}");
                }
            } else {
                throw new Exception($data['return_msg']);
            }
        } else {
            throw new Exception("fail to get prepay_id");
        }
    }


    /**
     * 订单参数检查过滤
     * @param  array  $p 订单参数
     * @return array     过滤后的参数
     */
    private function orderParamFilter($p=array()) {
        //商品描述 最多32字节
        if(!isset($p['body'])) {
            throw new Exception("order body missing");
        } else {
            $p['body'] = preg_replace('/\s/', '', $p['body']);
            $p['body'] = cnsubStr($p['body'], 30);
        }
        //外部交易号
        if(!isset($p['out_trade_no'])) {
            throw new Exception("order out_trade_no missing");
        }
        //订单实付金额
        if(!isset($p['total_fee'])) {
            throw new Exception("order total_fee missing");
        } else {
            //注意单位为分
            $p['total_fee'] *= 100;
        }
        //交易类型
        if(isset($p['trade_type'])) {
            if( !in_array($p['trade_type'], self::$trade_type) ) {
                throw new Exception("illegal trade type");
            }
        } else {
            //默认JSAPI
            $p['trade_type'] = self::$trade_type[0];
        }

        if($p['trade_type']=='NATIVE' && !isset($p['product_id'])) {
            throw new Exception("product_id missing for NATIVE trade type");
        }
        if($p['trade_type']=='JSAPI' && !isset($p['openid'])) {
            throw new Exception("openid missing for JSAPI trade type");
        }

        return $p;
    }

    /*
     * 参数签名
     */
    private function getMD5Sign($params=array()) {
        //空值不签名
        foreach ($params as $k => $v) {
            if($v==='') {
                unset($params[$k]);
            }
        }
        ksort($params);
        $str = '';
        foreach ($params as $k => $v){
            $str .= $k.'='.$v.'&';
        }
        $str .= 'key='.$this->partner_key;
        
        return strtoupper(md5($str));
    }

    /*
     * 验证微信返回消息签名
     */
    private function checkReturnMsg($data=array()) {
        if(isset($data['sign'])) {
            $sign = $data['sign'];
            unset($data['sign']);
            if($sign===$this->getMD5Sign($data)) {
                return true;
            } else {
                throw new Exception("invalid sign");
            }
        } else {
            throw new Exception("empty sign");
        }
    }

    /*
     * 获取网页端发起支付所需参数
     */
    public function getJSAPIParam($prepay_id="") {
        $arr = array(
            "appId"     => $this->appid,
            "timeStamp" => TIME."",
            "nonceStr"  => getRandStr(),
            "package"   => "prepay_id=".$prepay_id,
            "signType"  => "MD5",
        );
        $arr['paySign'] = $this->getMD5Sign($arr);

        return $arr;
    }

    /*
     * 获取App端发起支付所需参数
     */
    public function getAppParam($prepay_id="") {
        $arr = array(
            "appId"     => $this->appid,
            "partnerId" => $this->mch_id,
            "prepayId"  => $prepay_id,
            "nonceStr"  => getRandStr(),
            "timeStamp" => TIME."",
            "package"   => "prepay_id=".$prepay_id,
        );
        $arr['sign'] = $this->getMD5Sign($arr);

        return $arr;
    }
    
    public function setNotifyURL($url) {
        $this->notify_url = $url;
    }

    public function getNotifyURL() {
        return $this->notify_url;
    }

    /*
     * 微信通知数据校验
     */
    public function checkNotify() {
        $xml  = file_get_contents('php://input');
        $data = (array)simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);

        $return_code = isset($data['return_code']) ? $data['return_code'] : null;
        $return_msg  = isset($data['return_msg']) ? $data['return_msg'] : 'empty error msg';

        if($return_code==='SUCCESS') {
            if($this->checkReturnMsg($data)) {
                return $data;
            }
        } else {
            throw new Exception($return_msg);
        }

    }

}